// Copyright 2010 Alexandre Tristan St-Cyr <tristan.stcyr@gmail.com>

package com.codeplex.loadjava;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.logging.Level;
import java.util.logging.Logger;


class JavaRepository extends Repository implements IJavaRepository {

	private static final Logger logger = Logger.getLogger(JavaRepository.class.getCanonicalName());
	
	boolean isBatchInsertEnabled = true;
	
	private static final String INSERT_CLASS_DEF = 
		"insert into [Java.Class].ClassDefinitions (Id, Folder, IsPublic, " +
		"IsInterface, IsAbstract, IsFinal, IsSuper, IsAnnotation, IsEnum, " +
		"IsSynthetic, SuperClass, Signature, OuterClass, PackageName) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);";
	
	private static final String INSERT_CLASS = 
		"insert into [Java.Class].Classes (Id, InternalName, Folder) values(?, ?, ?);";
	
	private static final String INSERT_FIELD_DEF =
		"insert into [Java.Class].FieldDefinitions (Id, Folder, Name, Signature, Type, Access, IsFinal, IsVolatile, " +
		"IsTransient, IsStatic)" +
		" values( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
	
	private static final String INSERT_FIELD = 
		"insert into [Java.Class].Fields (Id, Folder, Name, Class, Type) " +
		"values(?, ?, ?, ?, ?);";
	
	private static final String INSERT_TYPE = 
		"insert into [Java.Class].Types (Id, Folder, TypeKind, " +
		"ArrayDimensions, Class, Descriptor) values(?, ?, ?, ?, ?, ?);";
	
	private static final String INSERT_METHOD = 
		"insert into [Java.Class].Methods (Id, Folder, Name, " +
		"Descriptor, Class) values(?, ?, ?, ?, ?);";
	
	private static final String INSERT_METHOD_DEF = 
		"insert into [Java.Class].MethodDefinitions (Id, Folder, Access, IsSynchronized," +
		" IsNative, IsAbstract, IsStrict, Signature) " +
		"values(?, ?, ?, ?, ?, ?, ?, ?);";

	private static final String INSERT_FIELD_REF = 
		"insert into [Java.Class].FieldReferences (Folder, Method, Field) " +
		"values(?, ?, ?);";
	
	private static final String INSERT_METHOD_INVOC = 
		"insert into [Java.Class].MethodInvocations (Folder, Caller, Callee) " +
		"values(?, ?, ?);";
	
	private static final String INSERT_INTERF_IMPL = 
		"insert into [Java.Class].InterfaceImplementations (Folder, Class, Interface) " +
		"values(?, ?, ?);";
	
	private static final String INSERT_METHOD_PARAM = 
		"insert into [Java.Class].MethodParameters (Folder, [Index], MethodDefinition, Type) " +
		"values(?, ?, ?, ?);";
	
	IJavaLoaderSettings settings;
	
	private PreparedStatement insertClassesStmt;
	
	private PreparedStatement insertClassDefStmt;
	
	private PreparedStatement insertFieldStmt;
	
	private PreparedStatement insertFieldDefStmt;

	private PreparedStatement insertTypeStmt;

	private PreparedStatement insertMethodStmt;

	private PreparedStatement insertFieldRefStmt;

	private PreparedStatement insertMethodDefStmt;

	private PreparedStatement insertMethodInvocStmt;

	private PreparedStatement insertInterfImplStmt;

	private PreparedStatement insertMethodParamStmt;
	
	public JavaRepository(IJavaLoaderSettings settings)
	{
		super(settings.getConnection());
		this.settings = settings;
	}
	
	public void setEnableBatchInsert(boolean isBatchInsertEnabled) {
		this.isBatchInsertEnabled = isBatchInsertEnabled;
	}
	
	@Override
	public void insertClass(long id, String name) throws SQLException
	{
		if (insertClassesStmt == null)
			insertClassesStmt = settings.getConnection().prepareStatement(INSERT_CLASS);
		insertClassesStmt.setLong(1, id);
		insertClassesStmt.setString(2, name);
		insertClassesStmt.setLong(3, this.settings.getFolderId());
		
		if (!isBatchInsertEnabled)
			insertClassesStmt.execute();
		else
			insertClassesStmt.addBatch();
	}

	@Override
	public void executeBatchInserts() throws SQLException {
		if (this.isBatchInsertEnabled)
		{
			logger.log(Level.INFO, "Batch inserting into database.");
			// Array is in order of dependencies
			Statement[] statements = new Statement[] {
					this.insertClassesStmt, 
					this.insertTypeStmt,
					this.insertClassDefStmt,
					this.insertFieldStmt,
					this.insertFieldDefStmt,
					this.insertMethodStmt,
					this.insertMethodDefStmt,
					this.insertMethodInvocStmt,
					this.insertFieldRefStmt,
					this.insertInterfImplStmt
			};
			for(Statement statement : statements)
			{
				if (statement != null)
				{
					statement.executeBatch();
				}
			}
			logger.log(Level.INFO, "Done inserting into database.");
		}
	}

	@Override
	public void insertField(long id, long classId, String fieldName,
			Long typeId) throws SQLException {
		if (insertFieldStmt == null)
			insertFieldStmt = settings.getConnection().prepareStatement(INSERT_FIELD);
		insertFieldStmt.setLong(1, id);
		insertFieldStmt.setLong(2, this.settings.getFolderId());
		insertFieldStmt.setString(3, fieldName);
		insertFieldStmt.setLong(4, classId);
		insertFieldStmt.setLong(5, typeId);
		
		if (!isBatchInsertEnabled)
			insertFieldStmt.execute();
		else
			insertFieldStmt.addBatch();
	}
	
	@Override
	public void insertFieldDefinition(long id, String name,
			String signature, long typeId, int access, boolean isStatic,
			boolean isFinal, boolean isVolatile, boolean isTransient)
			throws SQLException {
		if (insertFieldDefStmt == null)
			insertFieldDefStmt = settings.getConnection().prepareStatement(INSERT_FIELD_DEF);
		insertFieldDefStmt.setLong(1, id);
		insertFieldDefStmt.setLong(2, this.settings.getFolderId());
		insertFieldDefStmt.setString(3, name);
		insertFieldDefStmt.setString(4, signature);
		insertFieldDefStmt.setLong(5, typeId);
		insertFieldDefStmt.setInt(6, access);
		insertFieldDefStmt.setBoolean(7, isFinal);
		insertFieldDefStmt.setBoolean(8, isVolatile);
		insertFieldDefStmt.setBoolean(9, isTransient);
		insertFieldDefStmt.setBoolean(10, isStatic);
		
		if (!isBatchInsertEnabled)
			insertFieldDefStmt.execute();
		else
			insertFieldDefStmt.addBatch();
	}

	@Override
	public void insertClassDefinition(long id, String signature,
			boolean isInterface, boolean isAbstract, boolean isFinal,
			boolean isPublic, boolean isSuper, boolean isAnnotation,
			boolean isEnum, boolean isSynthetic, long superClassTypeId,
			Long outerClass, String packageName) throws SQLException {
		if (insertClassDefStmt == null)
			insertClassDefStmt = settings.getConnection().prepareStatement(INSERT_CLASS_DEF);
		
		insertClassDefStmt.setLong(1, id);
		insertClassDefStmt.setLong(2, this.settings.getFolderId());
		insertClassDefStmt.setBoolean(3, isPublic);
		insertClassDefStmt.setBoolean(4, isInterface);
		insertClassDefStmt.setBoolean(5, isAbstract);
		insertClassDefStmt.setBoolean(6, isFinal);
		insertClassDefStmt.setBoolean(7, isSuper);
		insertClassDefStmt.setBoolean(8, isAnnotation);
		insertClassDefStmt.setBoolean(9, isEnum);
		insertClassDefStmt.setBoolean(10, isSynthetic);
		insertClassDefStmt.setLong(11, superClassTypeId);
		insertClassDefStmt.setString(12, signature);
		if (outerClass != null)
			insertClassDefStmt.setLong(13, outerClass);
		else
			insertClassDefStmt.setNull(13, Types.BIGINT);
		insertClassDefStmt.setString(14, packageName);
		
		if (!isBatchInsertEnabled)
			insertClassDefStmt.execute();
		else
			insertClassDefStmt.addBatch();
	}

	@Override
	public void insertType(long id, char kind, int arrayDimensions,
			Long classId, String descriptor) throws SQLException {
		if (insertTypeStmt == null)
			insertTypeStmt = settings.getConnection().prepareStatement(INSERT_TYPE);
		
		insertTypeStmt.setLong(1, id);
		insertTypeStmt.setLong(2, this.settings.getFolderId());
		insertTypeStmt.setString(3, kind+"");
		insertTypeStmt.setInt(4, arrayDimensions);
		if (classId != null)
			insertTypeStmt.setLong(5, classId);
		else
			insertTypeStmt.setNull(5, Types.BIGINT);
		
		insertTypeStmt.setString(6, descriptor);

		if (!isBatchInsertEnabled)
			insertTypeStmt.execute();
		else
			insertTypeStmt.addBatch();
	}
	
	@Override
	public void insertMethod(long id, String methodName,
			String descriptor, long classId) throws SQLException {
		if (insertMethodStmt == null)
			insertMethodStmt = settings.getConnection().prepareStatement(INSERT_METHOD);
		
		insertMethodStmt.setLong(1, id);
		insertMethodStmt.setLong(2, this.settings.getFolderId());
		insertMethodStmt.setString(3, methodName);
		insertMethodStmt.setString(4, descriptor);
		insertMethodStmt.setLong(5, classId);

		if (!isBatchInsertEnabled)
			insertMethodStmt.execute();
		else
			insertMethodStmt.addBatch();
	}

	@Override
	public void insertFieldReference(long methodId, long fieldId) throws SQLException {
		if (insertFieldRefStmt == null)
			insertFieldRefStmt = settings.getConnection().prepareStatement(INSERT_FIELD_REF);
		
		insertFieldRefStmt.setLong(1, this.settings.getFolderId());
		insertFieldRefStmt.setLong(2, methodId);
		insertFieldRefStmt.setLong(3, fieldId);

		if (!isBatchInsertEnabled)
			insertFieldRefStmt.execute();
		else
			insertFieldRefStmt.addBatch();
	}

	@Override
	public void insertMethodDefinition(long methodId, int access,
			boolean isSynchronized, boolean isNative, boolean isAbstract,
			boolean isStrict, String signature) throws SQLException {
		if (insertMethodDefStmt == null)
			insertMethodDefStmt = settings.getConnection().prepareStatement(INSERT_METHOD_DEF);
		
		insertMethodDefStmt.setLong(1, methodId);
		insertMethodDefStmt.setLong(2, this.settings.getFolderId());
		insertMethodDefStmt.setLong(3, access);
		insertMethodDefStmt.setBoolean(4, isSynchronized);
		insertMethodDefStmt.setBoolean(5, isNative);
		insertMethodDefStmt.setBoolean(6, isAbstract);
		insertMethodDefStmt.setBoolean(7, isStrict);
		insertMethodDefStmt.setString(8, signature);
		
		if (!isBatchInsertEnabled)
			insertMethodDefStmt.execute();
		else
			insertMethodDefStmt.addBatch();
		
	}
	
	@Override
	public void insertMethodInvocation(long caller, long callee) throws SQLException {
		if (insertMethodInvocStmt == null)
			insertMethodInvocStmt = settings.getConnection().prepareStatement(INSERT_METHOD_INVOC);
		
		insertMethodInvocStmt.setLong(1, this.settings.getFolderId());
		insertMethodInvocStmt.setLong(2, caller);
		insertMethodInvocStmt.setLong(3, callee);
		if (!isBatchInsertEnabled)
			insertMethodInvocStmt.execute();
		else
			insertMethodInvocStmt.addBatch();
		
	}

	@Override
	public void insertInterfaceImplementation(long implementationId,
			long interfaceId) throws SQLException {
		if (insertInterfImplStmt == null)
			insertInterfImplStmt = settings.getConnection().prepareStatement(INSERT_INTERF_IMPL);
		
		insertInterfImplStmt.setLong(1, this.settings.getFolderId());
		insertInterfImplStmt.setLong(2, implementationId);
		insertInterfImplStmt.setLong(3, interfaceId);
		if (!isBatchInsertEnabled)
			insertInterfImplStmt.execute();
		else
			insertInterfImplStmt.addBatch();
	}

	@Override
	public void insertMethodParameter(int index, long methodId, long typeId) throws SQLException {
		if (insertMethodParamStmt == null)
			insertMethodParamStmt = settings.getConnection().prepareStatement(INSERT_METHOD_PARAM);
		
		insertMethodParamStmt.setLong(1, this.settings.getFolderId());
		insertMethodParamStmt.setInt(2, index);
		insertMethodParamStmt.setLong(3, methodId);
		insertMethodParamStmt.setLong(4, typeId);
		if (!isBatchInsertEnabled)
			insertMethodParamStmt.execute();
		else
			insertMethodParamStmt.addBatch();
	}
}
